import time

import pyglet
from pyglet.gl import *
from pyglet.window import key

from cocos.director import director
from cocos.layer import *
from cocos.scene import *
from cocos.sprite import Sprite
from cocos.actions import Show, Hide

from squirtle_mod import squirtle_cocos_adaptor as sqa

import gg 
from actors_view import View_objs_Factory

class HUD(Layer):
    def __init__(self, world_model):
        super( HUD, self).__init__()
        self.anchor = (0.0,0.0)
        self.world_model = world_model
        self.help_sp = Sprite('data/hud/help.png')
        self.stats_sp = Sprite('data/hud/stats.png', anchor=(0.0, 0.0))
        self.win_sp = Sprite('data/hud/win.png')
        self.lose_sp = Sprite('data/hud/ouch.png')
        self.world_model.push_handlers(self.on_help_request,
                                       self.on_help_end,
                                       self.on_game_over,
                                       self.on_next_stage,
                                       self.on_stats_changed)

    def on_enter(self):
        w,h = director.get_window_size()
        self.stats_sp.position = (10.0, 10.0)
        self.stats_sp.do(Hide())
        self.add(self.stats_sp)
        self.add(self.help_sp)
        self.on_help_request() # begin with help displayed
        super(HUD,self).on_enter()

    # downward are all handlers for world_model events
    def on_help_request(self):
        self.stats_sp.do(Hide())
        w,h = director.get_window_size()
        self.help_sp.position = (w/2,h/2)        
        self.help_sp.do(Show())
        
    def on_help_end(self):
        self.help_sp.do(Hide())
        self.stats_sp.do(Show())

    def on_game_over(self, bWin):
        # ouch - you win messages
        w,h = director.get_window_size()
        if bWin:
            self.win_sp.position = (w/2,h/2)
            self.add(self.win_sp)
        else:
            self.lose_sp.position = (w/2,h/2)
            self.add(self.lose_sp)

    def on_next_stage(self):
        director.pop()

    def on_stats_changed(self, score, stage):
        pass
        
class CameraLayer(Layer):
    is_event_handler = True
    def __init__(self, world_model):
        super(Layer,self).__init__()
        self.anchor = (0,0)
        self.world_model = world_model

    def on_enter(self):
        super(CameraLayer,self).on_enter()
        w,h = director.get_window_size()
        self.scale = h/self.world_model.height
        self.world_model.push_handlers(self.on_pov_changed)
        
    def own_to_parent_coords(self, x, y):
        px = (x - self.anchor_x) * self.scale
        py = (y - self.anchor_y) * self.scale
        return px, py

    def parent_to_own_coords(self, px, py):
        x = px / self.scale + self.anchor_x
        y = py / self.scale + self.anchor_y
        return x, y

    def map_world_to_sc( self, wx, wy, sx, sy ):
        """
        set up the camera so that point wx,wy in world coords shows
        at coords sx,sy in screen
        """
        # we want sx,sy = own_to_parent(wx,wy), working in the equation
        self.anchor_x = wx-sx/self.scale
        self.anchor_y = wy-sy*self.scale
        
    def on_pov_changed(self, wx):
        """ the screen bottom left will show the world at point (wx, 0)"""
        # we want the (wx, 0) be transformed to (0, 0)
        self.map_world_to_sc(wx, 0.0, 0.0, 0.0)


class WorldController(Layer):
    """
    set_bindings must be called so that the app recognizes user input
    model must reset any button desired to work as one-shot signal (ex: load)
    It cannot process events until the bindings are set, so at init time
    is_event_handler must be False.
    While it can be enabled after setting the binding, theres no real need until
    the scene is ready, so event and scheduling is enabled in relay_scene_ready 
    """
    is_event_handler = False # starting value
    def __init__(self, world_model):
        super( WorldController, self).__init__()
        self.paused = True
        self.world_model = world_model
        self.joysticks = []

    def handle_settings(self, controller_creation_info):
        self.set_key_bindings(controller_creation_info['kbd_bindings'])
        self.set_joy_bindings(controller_creation_info['joy_bindings'],
                              controller_creation_info['joy_to_clear'])
        self.enable_coop_sp = controller_creation_info['enable_coop_sp']
        
    def set_key_bindings(self, bindings):
        """
        bindings : a dict holding <pyglet key const>:(dest_dict, command) pairs
        buttons : model get user input through this member.
            it is a two level dict with the first key selecting the
            group - destination, the second key selecting the command - signal
            and the value is 1 if pressed, 0 if not
            Actual destinations: 'world', 'ctroller1', 'ctroller2'
        """
        buttons = {}
        for k in bindings:
            dest, cmd = bindings[k]
            if dest not in buttons:
                buttons[dest] = {}
            buttons[dest][cmd] = 0
        self.bindings = bindings
        self.buttons = buttons

    def set_joy_bindings(self, joy_bindings, joy_to_clear):
        for j, binds in self.joysticks:
            j.close()
        self.joysticks = joy_bindings
        self.joy_to_clear = joy_to_clear
        for j, binds in self.joysticks:
            j.open()
        time.sleep(0.1) #? tentative workaround joystick crash
        buttons = self.buttons
        for dest, cmd in self.joy_to_clear:
            if dest not in buttons:
                buttons[dest] = {}
            buttons[dest][cmd] = 0

    #tracking of keys : keys updates self.buttons, model uses buttons
    def on_key_press(self, k, m):
        binds = self.bindings
        if k in binds:
            dest, cmd = binds[k]
            self.buttons[dest][cmd] = 1
            return True
        return False

    def on_key_release(self, k, m):
        binds = self.bindings
        if k in binds:
            dest, cmd = binds[k]
            self.buttons[dest][cmd] = 0
            return True
        return False

    def on_pause_changed(self, paused): # comes from model
        self.paused = paused

    def relay_scene_ready(self):
        # allow self to process events
        self.is_event_handler = True
        self.push_all_handlers()
        self.schedule(self.step)
        self.world_model.start()

    def step(self, dt):
        self.update_buttons_from_joystick()
        self.world_model.update(dt)

    def update_buttons_from_joystick(self):
        buttons = self.buttons
        for dest, cmd in self.joy_to_clear:
            buttons[dest][cmd] = 0
        for joystick, jbinds in self.joysticks:
            for control in joystick.device.get_controls():
                if control.name in jbinds:
                    fn_aquire = jbinds[control.name]
                    fn_aquire(buttons, control)


class WorldView(Layer):
    is_event_handler = True
    def __init__(self, world_model):
        super( WorldView, self).__init__()
        self.anchor = (0.0, 0.0)
        self.world_model = world_model

        self.view_objs_by_name = {}
        # bg layer
        layer = Layer()
        layer.anchor = (0,0)
        self.add(layer, z=0, name='bg')
        # goal line layer
        layer = Layer()
        layer.anchor = (0,0)
        self.add(layer, z=1, name='goal_line')
        # goal zone layer, one above goal_line
        layer = Layer()
        layer.anchor = (0,0)
        self.add(layer, z=2, name='goal_line+')
        # svg layer ( player, pigeons, exit )
        node = sqa.SVG_CacheNode()
        self.add(node, z=3, name='svg')
        # special effects layer ( particles, ... )
        # NOTE: objs in this layer not to include in view_objs_by_name  
        layer = Layer()
        layer.anchor = (0,0)
        self.add(layer, z=4, name='fx')
        self.world_model.push_handlers(self.on_actor_deleted,
                           self.on_actor_changed,
                           self.on_actor_added )
        #debug
        self.deb_scale = 0.1
        self.schedule(self.pulsate_scaling)

    def on_enter(self):
        #? fast and dirty, make proper later
        w,h = director.get_window_size()
        self.unzoomed_viewport_width = w
        self.unzoomed_viewport_height = h
        super(WorldView,self).on_enter()

    def on_actor_deleted(self, idx):
        view_obj = self.view_objs_by_name.pop(idx)
        child_name = view_obj.parent_name
        child = self.get(view_obj.parent_name)
        child.remove(view_obj)

    def on_actor_changed(self, idx, info_update):
        view_obj = self.view_objs_by_name[idx]
        view_obj.update_from_model(info_update)

    def on_actor_added(self, info_creation, info_update):
        #print 'view - on actor added - info_creation, info update', info_creation, info_update
        view_obj = View_objs_Factory(self, info_creation, info_update) 
        child = self.get(view_obj.parent_name)
        child.add(view_obj)
        self.view_objs_by_name[view_obj.idx] = view_obj

    def get_dir(self, selector):
        return self.world_model.get_dir(selector)

    #debug
    def pulsate_scaling(self,dt):
        self.deb_scale += dt
        if self.deb_scale>0.4:
            self.deb_scale = 0.1
        svgs = self.get('svg')
        for e in svgs.get_children():
            e.scale = self.deb_scale
        
class GameScene(Scene):
    def on_enter(self):
        super(GameScene,self).on_enter()
        child = self.get('controller')
        child.relay_scene_ready()


import model
def get_game_scene(controller_creation_info, world_creation_info):
    """returns the world_model scene"""
    # model
    world_model = model.World(world_creation_info)

    # controller
    ctrl = WorldController( world_model )
    ctrl.handle_settings(controller_creation_info)

    # view
    scene = GameScene()
    scene.anchor = (0,0)
    hud = HUD(world_model) # receives stat updates and game over -> director.replace
    camera = CameraLayer(world_model)
    camera.add(WorldView(world_model))

    # set controller in model
    world_model.set_controller( ctrl )

    # add controller to view
    scene.add( ctrl, z=1, name="controller" )

    # add view
    scene.add( hud, z=3, name="hud" )
    scene.add( camera, z=2, name="view" )

    return scene


def test_game_scene():
    world_creation_info = {
        'players': [
            { 'mind_type': 'human',
              'button_group': 'ctroller0'
              },
            { 'mind_type': None,
              'button_group': 'orders'
              },
            ],
            
        'game_mode': 'sp',
        'pigeons_nowayback': True,
        'birds_nowayback': False,
        'stage': 0
        }

    kbd_bindings = { #key constant : (dest ,button name)
        key.LEFT:('ctroller0','left'),
        key.RIGHT:('ctroller0','right'),
        key.UP:('ctroller0','up'),
        key.DOWN:('ctroller0','down'),
        
        key.RETURN:('world','end_help'),
        key.H:('world','begin_help'),
        key.F1:('world','begin_help'),
        key.R:('world','restart'), #restart level
        key.N:('world','warp') # warp to next level
        }

    controller_creation_info = {
        'kbd_bindings': kbd_bindings,
        'joy_bindings': [],
        'joy_to_clear': [],
        'enable_coop_sp': False
        }

    return get_game_scene(controller_creation_info, world_creation_info)

# publish services
gg.services["s_get_game_scene"] = test_game_scene    
